using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Linq;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Framework;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.ADF.CATIDs;
using BesAsm.Emgaats.Core;
using BesAsm.Emgaats.ModelBuilder;
using BesAsm.MasterData.Entities;
using BesAsm.MasterData.Gis;
using BesAsm.MasterData.Services;
using BesAsm.Framework.ArcGisUtilities;
using ESRI.ArcGIS.DataSourcesGDB;
using ESRI.ArcGIS.ArcMapUI;
using BesAsm.ModelData.Data;
using BesAsm.ModelData.Entities;
using BesAsm.ModelData.Gis;
using BesAsm.ModelData.Services;
using BesAsm.Emgaats.Core.Commands;
using BesAsm.Emgaats.ModelBuilder.Commands;
using BesAsm.Framework.Reporting;

namespace BesAsm.Emgaats.ModelBuilderUI
{
  /// <summary>
  /// Adds model building services to ArcMap
  /// </summary>  
  [ClassInterface(ClassInterfaceType.None)]
  public class ModelBuilderExtension : ESRI.ArcGIS.Desktop.AddIns.Extension, IPersistVariant, IEmgaatsExtension
  {
    //TODO: This could be dynamic (e.g. show the model name) or read from config file
    public const string MODEL_DATA_GROUP_NAME = "Model Data";

    private EmgaatsList<Link> _startLinks;
    private EmgaatsList<Link> _stopLinks;
    private EmgaatsList<Link> _traceLinks;
    private ModelBuilderUserControl _modelBuilderUserControl;

    private Mediator _mediator;
    private LayerLibrarian _librarian;

    /// <summary>
    /// Raised when one of the extension's data sources changes
    /// </summary>
    public event EventHandler RebindRequired;

    private static ModelBuilderExtension _modelBuilderExtension;

    public ModelBuilderExtension()
    {
      _modelBuilderExtension = this;
    }

    internal static ModelBuilderExtension GetExtension()
    {
      if (_modelBuilderExtension == null)
      {
        UID uid = new UIDClass();
        uid.Value = ThisAddIn.IDs.ModelBuilderExtension;
        ArcMap.Application.FindExtensionByCLSID(uid);
      }
      return _modelBuilderExtension;
    }

    #region IPersistVariant Members

    /// <summary>
    /// ID of persistence stream
    /// </summary>
    public UID ID
    {
      get
      {
        UID typeID = new UIDClass();
        typeID.Value = GetType().GUID.ToString("B");
        return typeID;
      }
    }

    /// <summary>
    /// Called by ArcMap to load data
    /// </summary>
    /// <param name="Stream"></param>
    public void Load(IVariantStream Stream)
    {
      //TODO: Load persisted data from document stream

      Marshal.ReleaseComObject(Stream);
    }

    /// <summary>
    /// Called by ArcMap to save data
    /// </summary>
    /// <param name="Stream"></param>
    public void Save(IVariantStream Stream)
    {
      //TODO: Save extension related data to document stream

      Marshal.ReleaseComObject(Stream);
    }

    #endregion

    /// <summary>
    /// Provides a means for this extension to perform custom startup
    /// </summary>
    /// <param name="initializationData"></param>
    protected override void OnStartup()
    {
      base.OnStartup();

      _mediator = Mediator.AttachToMediator(this);
      _librarian = LayerLibrarian.AttachToLibrarian(this);

      BindLibrarian();
      Mediator.ModelLoaded += ModelBuilderExtension_ModelLoaded;
      LayerLibrarian.CheckedIn += ModelBuilderExtension_TraceLinksChanged;
    }

    /// <summary>
    /// Handles the LayerLibrarian CheckIn event
    /// </summary>
    /// <param name="sender">The object raising the CheckIn event</param>
    /// <param name="e">A CheckInCheckOutArgs object indicating which layer was checked in</param>
    protected void OnCheckIn(object sender, CheckInCheckOutArgs e)
    {
      EmgaatsLayer emgLayer = new EmgaatsLayer(e.LibraryItem, e.Layer);

      switch (emgLayer.LibraryItem)
      {
        case LibraryItem.StartLinks:
          _startLinks = new EmgaatsList<Link>(ArcMap.Application, emgLayer);
          _startLinks.SyncListToMap(new LinkService());
          break;
        case LibraryItem.StopLinks:
          _stopLinks = new EmgaatsList<Link>(ArcMap.Application, emgLayer);
          _stopLinks.SyncListToMap(new LinkService());
          break;
        case LibraryItem.TraceLinks:
          _traceLinks = new EmgaatsList<Link>(ArcMap.Application, emgLayer);
          _traceLinks.SyncListToMap(new LinkService());
          break;
        default:
          return;
      }
      OnRebindRequired(EventArgs.Empty);
    }

    /// <summary>
    /// Handles the LayerLibrarian CheckOut event
    /// </summary>
    /// <param name="sender">The object raisingg the CheckOut event</param>
    /// <param name="e">A CheckOutCheckOutArgs object indicating which layer was checked out</param>
    protected void OnCheckOut(object sender, CheckInCheckOutArgs e)
    {
      switch (e.LibraryItem)
      {
        case LibraryItem.StartLinks:
          _startLinks = null;
          break;
        case LibraryItem.StopLinks:
          _stopLinks = null;
          break;
        case LibraryItem.TraceLinks:
          _traceLinks = null;
          break;
        default:
          return;
      }
      OnRebindRequired(EventArgs.Empty);
    }

    /// <summary>
    /// Raised when one of the Tracer Extensions data sources (StartLinks,
    /// StopLinks, TraceLinks or MasterLinks) has been added or removed
    /// from the ArcGIS session.
    /// </summary>
    /// <param name="e">Default event arg, not used here</param>
    protected void OnRebindRequired(EventArgs e)
    {
      if (RebindRequired != null)
        RebindRequired(this, e);
    }

    /// <summary>
    /// Starts event-binding to the LayerLibrarian events
    /// </summary>
    private void BindLibrarian()
    {
      LayerLibrarian.CheckedIn += OnCheckIn;
      LayerLibrarian.CheckedOut += OnCheckOut;
      OnRebindRequired(EventArgs.Empty);
    }

    /// <summary>
    /// Stops event-binding to the LayerLibrarian events
    /// </summary>
    private void UnbindLibrarian()
    {
      LayerLibrarian.CheckedIn -= OnCheckIn;
      LayerLibrarian.CheckedOut -= OnCheckOut;
    }

    /// <summary>
    /// Gets the model.
    /// </summary>
    internal Model Model
    {
      get { return Mediator.Model; }
    }

    /// <summary>
    /// Builds the model.
    /// </summary>
    public void BuildModel()
    {
      DateTime startBuildTime = DateTime.Now;
      Messenger.ReportMessage("Building model", BesAsm.Framework.Reporting.ReportableMessageType.Info);
      Model.State = ModelState.Assembled;
      MultiCommand buildModelCommand = new MultiCommand();
      buildModelCommand.AddCommand(new PrepareModelDirectoryCommand(Model));
      buildModelCommand.AddCommand(new BuildPgdbCommand(Model, ArcMap.Application));
      buildModelCommand.AddCommand(new DeployStormwaterControlsCommand(Model));
      buildModelCommand.AddCommand(new DeployHydrologyCommand(Model));
      buildModelCommand.AddCommand(new DeployHydraulicsCommand(Model));
      buildModelCommand.Execute();

      ////TODO: Check that model is ready to build (trace data is available and model state is template or greater)
      ////If model not ready to build, throw new ModelStateException()      
      //PgdbBuilder pgdbBuilder = new PgdbBuilder(Model);
      //pgdbBuilder.WritePgdb();

      //Model.State = ModelState.Assembled;

      SetModelDataSource();
      LoadModelLayersToMap();

      DateTime endBuildTime = DateTime.Now;
      TimeSpan buildDuration = endBuildTime.Subtract(startBuildTime);
      Messenger.ReportMessage(
        string.Format("Build ended ({0}:{1:00}:{2:00}.{3:000})", buildDuration.Hours,
          buildDuration.Minutes, buildDuration.Seconds, buildDuration.Milliseconds),
        BesAsm.Framework.Reporting.ReportableMessageType.Info);
    }

    public void SetModelDataSource()
    {
      _librarian.ModelDataSource = Model.GetPathIfSet(ModelPathComponent.Pgdb);
      ModelProviderInitializer.InitializeModelProvider(Model.GetPathIfSet(ModelPathComponent.Pgdb));
    }

    /// <summary>
    /// Loads the current model's layers to the active map
    /// </summary>
    public void LoadModelLayersToMap()
    {
      string modelLayersPath = Model.GetPathIfSet(ModelPathComponent.RootFolder);
      string modelLayersFilename = _librarian.ModelDataSource;

      IWorkspaceFactory workspaceFactory = new AccessWorkspaceFactoryClass();
      IFeatureWorkspace fws = (IFeatureWorkspace)workspaceFactory.OpenFromFile(modelLayersFilename, 0);

      ArcGisUtils arcUtils = new ArcGisUtils(ArcMap.Application);

      List<LibraryItem> modelItems = LayerLibrarian.ModelLibraryItems.ToList();

      foreach (LibraryItem item in modelItems)
      {
        string itemValue = _librarian.LayerCatalog[item];
        IFeatureClass featureClass = (IFeatureClass)fws.OpenFeatureClass(itemValue);
        IFeatureLayer featureLayer = new FeatureLayerClass(); ;
        featureLayer.FeatureClass = featureClass;
        featureLayer.Name = itemValue;
        //arcUtils.AddLayerToMap(featureLayer);
        arcUtils.MoveLayerToGroup(featureLayer, MODEL_DATA_GROUP_NAME);
      }
    }

    private void ModelBuilderExtension_ModelLoaded(object sender, EventArgs e)
    {
      //TODO: Perform any necessary actions when the model changes
      //NOTE: Functionality that was present here has been assigned to ModelBuilderUserContorl
    }

    private void ModelBuilderExtension_TraceLinksChanged(object sender, CheckInCheckOutArgs e)
    {
      //TODO: Perform any necessary actions when the trace data changes
      //NOTE: Functionality that was present here has been assigned to ModelBuilderUserContorl      
    }

    /// <summary>
    /// Checks for whether the extension is compatible with the system (master database)
    /// </summary>
    public bool IsExtensionCompatibleWithSystem
    {
      get
      {
        return Properties.Settings.Default.ExtensionSystemVersion ==
          Mediator.SystemVersion;
      }
    }

    public string Caption
    {
      get
      {
        return "Model Builder";
      }
    }

    public void ActivateExtension()
    {
      UID uid = new UIDClass();
      uid.Value = ThisAddIn.IDs.ModelBuilderUserControl;
      IDockableWindow win = ArcMap.DockableWindowManager.GetDockableWindow(uid);
      win.Show(true);
    }

    public bool Enabled
    {
      get { return IsExtensionCompatibleWithSystem; }
    }

  }
}